<?php

class SolarToLunar
{
    // 农历数据数组，存储了每年的农历信息
    private const LUNAR_INFO = [
        0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
        0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
        0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
        0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
        0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
        0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
        0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
        0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
        0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
        0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
        0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
        0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
        0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
        0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
        0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0
    ];

    // 农历月份名称数组
    private const LUNAR_MONTH_NAMES = ["正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"];
    // 农历日期名称数组
    private const LUNAR_DAY_NAMES = ["初", "十", "廿", "卅"];
    // 天干数组
    private const GAN = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
    // 地支数组
    private const ZHI = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
    // 特殊农历月份名称映射
    private const SPECIAL_MONTH_NAMES = [
        "正" => "正月",
        "十二" => "腊月"
    ];
    // 日期数字到汉字的映射
    private const DAY_NAME_MAP = [
        '1' => '一', '2' => '二', '3' => '三', '4' => '四',
        '5' => '五', '6' => '六', '7' => '七', '8' => '八',
        '9' => '九', '0' => '十'
    ];

    // 获取农历月份天数的函数
    private function getLunarMonthDays($year, $month)
    {
        $bit = 0x8000;
        if ($month > 12) {
            // 如果是闰月，判断该年闰月是否有 30 天
            return ((self::LUNAR_INFO[$year - 1900] & 0x10000) ? 30 : 29);
        }
        // 普通月份，根据农历信息判断该月天数
        return ((self::LUNAR_INFO[$year - 1900] & ($bit >> $month)) ? 30 : 29);
    }

    // 获取农历闰月的函数
    private function getLeapMonth($year)
    {
        return (self::LUNAR_INFO[$year - 1900] & 0xf);
    }

    // 获取农历年份总天数的函数
    private function getLunarYearDays($year)
    {
        $sum = 348;
        for ($i = 0x8000; $i > 0x8; $i >>= 1) {
            // 根据农历信息累加每个月的天数
            $sum += ((self::LUNAR_INFO[$year - 1900] & $i) ? 1 : 0);
        }
        // 如果有闰月，加上闰月的天数
        return $sum + $this->getLeapMonthDays($year);
    }

    // 获取农历闰月天数的函数
    private function getLeapMonthDays($year)
    {
        if ($this->getLeapMonth($year)) {
            // 如果该年有闰月，判断闰月是 30 天还是 29 天
            return ((self::LUNAR_INFO[$year - 1900] & 0x10000) ? 30 : 29);
        }
        return 0;
    }

    // 生成农历日期名称的函数
    private function generateLunarDayName($lDay)
    {
        if ($lDay <= 10) {
            return self::LUNAR_DAY_NAMES[0] . self::DAY_NAME_MAP[strval($lDay)];
        } elseif ($lDay > 10 && $lDay <= 20) {
            if ($lDay == 20) {
                return '二十';
            }
            return self::LUNAR_DAY_NAMES[1] . self::DAY_NAME_MAP[substr($lDay, -1)];
        } elseif ($lDay > 20 && $lDay <= 30) {
            if ($lDay == 30) {
                return '三十';
            }
            return self::LUNAR_DAY_NAMES[2] . self::DAY_NAME_MAP[substr($lDay, -1)];
        }
        return '';
    }

    // 公历转农历的函数
    public function convert($year, $month, $day)
    {
        // 检查输入日期的有效性
        if (!checkdate($month, $day, $year)) {
            throw new \InvalidArgumentException("输入的公历日期无效：{$year}-{$month}-{$day}");
        }

        $offset = mktime(0, 0, 0, $month, $day, $year) - mktime(0, 0, 0, 1, 31, 1900);
        $days = intval($offset / 86400);
        $lYear = 1900;
        $temp = $this->getLunarYearDays($lYear);
        while ($days >= $temp) {
            $days -= $temp;
            $lYear++;
            $temp = $this->getLunarYearDays($lYear);
        }
        $lMonth = 1;
        $leap = $this->getLeapMonth($lYear);
        $isLeap = false;
        while ($days >= $this->getLunarMonthDays($lYear, $lMonth)) {
            if ($leap > 0 && $lMonth == ($leap + 1) &&!$isLeap) {
                $isLeap = true;
                $lMonth--;
            }
            $days -= $this->getLunarMonthDays($lYear, $lMonth);
            if ($isLeap && $lMonth == $leap) {
                break;
            }
            $lMonth++;
            if (!$isLeap && $leap > 0 && $lMonth == ($leap + 1)) {
                $isLeap = true;
            }
        }
        $lDay = $days + 1;
        $lMonthName = self::LUNAR_MONTH_NAMES[$lMonth - 1];
        if ($isLeap) {
            $lMonthName = "闰" . $lMonthName;
        }
        if (isset(self::SPECIAL_MONTH_NAMES[$lMonthName])) {
            $lMonthName = self::SPECIAL_MONTH_NAMES[$lMonthName];
        }
        $lDayName = $this->generateLunarDayName($lDay);
        $ganZhiYear = self::GAN[($lYear - 4) % 10] . self::ZHI[($lYear - 4) % 12];
        return [
            "lunarYear" => $lYear,
            "lunarMonth" => $lMonth,
            "lunarDay" => $lDay,
            "lunarMonthName" => $lMonthName,
            "lunarDayName" => $lDayName,
            "ganZhiYear" => $ganZhiYear,
            "isLeap" => $isLeap
        ];
    }
}